package parse_and_index.search.inverted;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;

import parse_and_index.build.inverted.InvertedIndex;
import parse_and_index.model.Document;
import parse_and_index.model.Page;
import parse_and_index.model.PostingListNode;
import parse_and_index.model.TermFrequency;

public class QueryProcessor {

	  private final ArrayList<Score> scores;

	  public QueryProcessor() {
			scores = new ArrayList<Score>();
	  }

	  public ArrayList<Result> runRetrievalStrategy(final Query query, final InvertedIndex idx, final int scType) {
			/* now lets find out what kind of similarity measure to run */
			switch (scType) {
				  case 0 :
						runDotProduct(query, idx);
						break;
				  default :
						System.out.println("Invalid SC Type");
						break;
			}
			/* now lets store the non-zero elements of score in the treemap */
			/* this sets things up nicely for the GUI */
			final ArrayList<Result> results = new ArrayList<Result>();
			for (final Score score : scores) {
				  if (score.getValue() != 0) {
						final int docID = score.getDocumentId();
						final Document currentDocument = idx.getDocument(docID);
						score.sortPagesByRelevance();
						results.add(new Result(score.getValue(), currentDocument, score.getPages()));
				  }
			}
			/* now lets sort the results */
			Collections.sort(results);
			Collections.reverse(results);
			return results;
	  }

	  private void runDotProduct(final Query query, final InvertedIndex idx) {
			final HashMap<String, TermFrequency> queryTerms = query.getQueryTerms();
			final NavigableSet<String> tokens = new TreeSet<String>(idx.getKeysSet());
			// tokens = (TreeSet<String>)idx.getKeysSet();
			final long numberOfDocuments = idx.getNumberOfDocuments();
			/* initialize the score array */
			/* loop through all query terms, get their posting lists */
			/* loop through the posting lists and update the score array */
			final Set<String> querySet = queryTerms.keySet();
			for (final String currentToken : querySet) {
				  findAndScoreHits(idx, queryTerms, tokens, numberOfDocuments, currentToken);
			}
	  }

	  private void findAndScoreHits(final InvertedIndex idx, final HashMap<String, TermFrequency> queryTerms, final NavigableSet<String> tokens, final long numberOfDocuments, final String currentToken) {
			final short qtf = queryTerms.get(currentToken).getTF();
			final int cut = (currentToken.length() > 6) ? currentToken.length() - 3 : 3;
			final String prefix = currentToken.substring(0, cut);
			final Set<String> filteredSet = tokens.tailSet(prefix);
			for (final String str : filteredSet) {
				  if (str.startsWith(prefix)) {
						final LinkedList<PostingListNode> currentPostingList = idx.getPostingList(str);
						if (currentPostingList == null) { /* if a term has no posting list, skip rest of the loop -- no score to update */
							  continue;
						}
						final int df = currentPostingList.size();
						double idf = (double) numberOfDocuments / df;
						idf = Math.log10(idf);
						final double idfSquared = idf * idf;
						for (final PostingListNode pln : currentPostingList) {
							  final int docID = (int) pln.getDocumentID();
							  final short tf = pln.getTF();
							  updateScore(docID, tf, qtf, idfSquared, pln.getPages(), prefix); /* increase score for document */
						}
				  }
			}
	  }

	  /* here's where we update the score for a document */
	  /* different weighting schemes could easily be incorporated here */
	  private void updateScore(final int docID, final short tf, final short qtf, double idfSquared, final ArrayList<Page> pgs, final String prefix) {
			printScoreParameters(docID, tf, qtf, idfSquared);
			float sum = 0;
			idfSquared = idfSquared == 0 ? 1 : idfSquared;
			for (final Score score : scores) {
				  if (score.getDocumentId() == docID) {
						sum = score.getValue();
						sum = sum + ((float) tf * (float) qtf * (float) idfSquared);
						score.setValue(sum);
						score.addPages(pgs, prefix);
						System.out.println("score " + score);
						return;
				  }
			}
			sum = sum + ((float) tf * (float) qtf * (float) idfSquared);
			scores.add(new Score(docID, sum, pgs, prefix));
			return;
	  }

	  private void printScoreParameters(final int docID, final short tf, final short qtf, final double idfSquared) {
			System.out.println("docID " + Integer.toString(docID));
			System.out.println("tf " + Short.toString(tf));
			System.out.println("qtf " + Short.toString(qtf));
			System.out.println("idfSquared " + Double.toString(idfSquared));
	  }
}